package com.cheer.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.UUID;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.cheer.bo.SendInfoBO;
import com.cheer.common.Constant;
import com.cheer.common.ResultEnum;
import com.cheer.dao.AccessTokenCacheDao;
import com.cheer.dao.ExMatchUserDao;
import com.cheer.dto.SendInfoUpdateDTO;
import com.cheer.dto.UserRegisterDTO;
import com.cheer.dto.UserUpdateDTO;
import com.cheer.entity.ExMatchUserEntity;
import com.cheer.exception.ServiceException;
import com.cheer.service.AuthService;
import com.cheer.vo.ExMatchUserVO;
import com.cheer.vo.ShareTicketVO;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Stopwatch;
import com.google.common.collect.Lists;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpSession;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.concurrent.TimeUnit;

/**
 * @author cheer
 */
@Service
@Slf4j
public class AuthServiceImpl implements AuthService {

    @Value("${weixin.appId}")
    private String appid;

    @Value("${weixin.appSecret}")
    private String secret;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private AccessTokenCacheDao accessTokenCacheDao;

    @Autowired
    private ExMatchUserDao exMatchUserDao;

    private Boolean dev;

    @Value("${spring.profiles.active}")
    public void setDev(String dev) {
        if("dev".equals(dev)){
            this.dev = true;
        }else{
            this.dev = false;
        }
    }


    @Override
    public void login(String code, HttpSession session) {

        if(Constant.IS_DEV){
            session.setAttribute(Constant.SESSION_USERID, 2L);
            session.setAttribute(Constant.SESSION_OPENID, "odBjpjrPZ-hWmEkoI1Enm0T7e9Oo");
            return;
        }
        // 查询openid
        AccessTokenResponse accessToken = getAccessTokenByCode(code);

        // 查询用户信息，是否关注公众号，头像
        String appAccessToken = accessTokenCacheDao.getAccessToken();
        if (StringUtils.isBlank(appAccessToken)) {
            throw new ServiceException("登录失败，请重新访问");
        }

        UserInfoResponse userInfo = getUserInfoByAccessToken(appAccessToken, accessToken.getOpenid(), 2);
        if (userInfo == null) {
            throw new ServiceException("登录失败，请重新访问");
        }
        if (userInfo.getSubscribe() != 1) {
            throw new ServiceException(ResultEnum.ACCOUNTS);
        }
        userInfo.setHeadimgurl("http://cdn.ithyxy.com/images/header20220905.gif");
        // 根据openid查询用户信息
        ExMatchUserEntity user = exMatchUserDao.selectOne(new QueryWrapper<ExMatchUserEntity>()
                .eq("openid", accessToken.getOpenid())
                .last("limit 1"));

        // 查询不到用户，说明此用户未注册
        if (user == null) {
            session.setAttribute("headImgUrl", userInfo.getHeadimgurl());
            session.setAttribute("openid", accessToken.getOpenid());

            throw new ServiceException(ResultEnum.NOT_REGISTER);
        }

        // 用户已注册
        if (StringUtils.equals(user.getHeadIcon(), userInfo.getHeadimgurl()) == false) {
            ExMatchUserEntity updateUser = new ExMatchUserEntity();
            updateUser.setId(user.getId());
            updateUser.setHeadIcon(userInfo.getHeadimgurl());
            exMatchUserDao.updateById(updateUser);
        }
        session.setAttribute("userId", user.getId());
    }

    @Override
    public void register(UserRegisterDTO userRegisterDTO, HttpSession session) {
        ExMatchUserEntity user = new ExMatchUserEntity();
        BeanUtils.copyProperties(userRegisterDTO, user);
        user.setOpenid(getOpenid(session));
        user.setHeadIcon(getHeadImgUrl(session));

        try {
            exMatchUserDao.insert(user);
            session.setAttribute("userId", user.getId());
        } catch (DuplicateKeyException e) {
            log.warn("用户已注册");
        }
    }

    @Override
    public String getOpenid(HttpSession session) {

        if(dev){
            return "odBjpjl1-0mjW5Gjc8YtCvBYHu8c";
        }

        if (session.getAttribute("openid") == null) {
            throw new ServiceException("会话已超时，请重新登录");
        }
        return (String) session.getAttribute("openid");
    }

    @Override
    public Long getUserId(HttpSession session) {
        if(dev){
            return 14475L;
        }
        if (session.getAttribute("userId") == null) {
            throw new ServiceException("会话已超时，请重新登录");
        }
        return (Long) session.getAttribute("userId");
    }

    @Override
    public String getHeadImgUrl(HttpSession session) {
        if (session.getAttribute("headImgUrl") == null) {
            throw new ServiceException("会话已超时，请重新登录");
        }
        return (String) session.getAttribute("headImgUrl");
    }

    @Override
    public ShareTicketVO share(String url) {
        try {
            url = URLDecoder.decode(url, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            log.error(e.getMessage(), e);
        }

        String ticket = accessTokenCacheDao.getTicket();
        String noncestr = UUID.randomUUID().toString(true).substring(0, 16);
        long timestamp = System.currentTimeMillis() / 1000;
        // 将参数排序并拼接字符串
        String str = "jsapi_ticket=" + ticket + "&noncestr=" + noncestr + "&timestamp=" + timestamp + "&url=" + url;
        String signature = DigestUtils.sha1Hex(str);

        ShareTicketVO shareTicketVO = new ShareTicketVO();
        shareTicketVO.setAppId(appid);
        shareTicketVO.setTimestamp(timestamp);
        shareTicketVO.setNonceStr(noncestr);
        shareTicketVO.setSignature(signature);
        return shareTicketVO;
    }

    @Override
    public ExMatchUserVO info(HttpSession session) {
        ExMatchUserEntity user = exMatchUserDao.selectById(getUserId(session));
        if (user == null) {
            return null;
        }

        ExMatchUserVO exMatchUserVO = new ExMatchUserVO();
        BeanUtil.copyProperties(user, exMatchUserVO);

        if (StringUtils.isNotBlank(user.getProvince()) && StringUtils.isNotBlank(user.getCity())
                && StringUtils.isNotBlank(user.getRegion()) && StringUtils.isNotBlank(user.getAddress())) {
            if (StringUtils.equals(user.getProvince(), user.getCity())) {
                exMatchUserVO.setFullAddress(StringUtils.join(Lists.newArrayList(user.getCity(),
                        user.getRegion(), user.getAddress()), " "));
            } else {
                exMatchUserVO.setFullAddress(StringUtils.join(Lists.newArrayList(user.getProvince(), user.getCity(),
                        user.getRegion(), user.getAddress()), " "));
            }
        }

        return exMatchUserVO;
    }

    @Override
    public ExMatchUserVO user(HttpSession session) {
        ExMatchUserEntity user = exMatchUserDao.selectById(getUserId(session));

        ExMatchUserVO exMatchUserVO = new ExMatchUserVO();
        exMatchUserVO.setUsername(user.getUsername());
        exMatchUserVO.setHeadIcon(user.getHeadIcon());
        exMatchUserVO.setLevel(user.getLevel());
        exMatchUserVO.setLotteryToken(user.getLotteryToken());
        return exMatchUserVO;
    }

    @Override
    public void updateUser(UserUpdateDTO userUpdateDTO, HttpSession session) {
        ExMatchUserEntity user = new ExMatchUserEntity();
        user.setId(getUserId(session));
        BeanUtil.copyProperties(userUpdateDTO, user);
        exMatchUserDao.updateById(user);
    }

    @Override
    public SendInfoBO getSendInfo(HttpSession session) {
        ExMatchUserEntity user = exMatchUserDao.selectById(getUserId(session));
        if (user == null) {
            return new SendInfoBO();
        }
        return new SendInfoBO()
                .setPhone(user.getPhone())
                .setProvince(user.getProvince())
                .setCity(user.getCity())
                .setRegion(user.getRegion())
                .setAddress(user.getAddress());
    }

    @Override
    public void updateSendInfo(SendInfoUpdateDTO sendInfoUpdateDTO, HttpSession session) {
        ExMatchUserEntity user = new ExMatchUserEntity();
        BeanUtils.copyProperties(sendInfoUpdateDTO, user);
        user.setId(getUserId(session));
        exMatchUserDao.updateById(user);
    }

    @Override
    public String getUserImage(String openid) {
        String appAccessToken = accessTokenCacheDao.getAccessToken();
        if (StringUtils.isBlank(appAccessToken)) {
            return null;
        }

        String url = String.format("https://api.weixin.qq.com/cgi-bin/user/info?access_token=%s&openid=%s&lang=zh_CN",
                appAccessToken, openid);

        try {
            ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
            if (responseEntity.getStatusCodeValue() == 200 && responseEntity.getBody() != null) {
                UserInfoResponse response = objectMapper.readValue(responseEntity.getBody(), UserInfoResponse.class);
                return response.getHeadimgurl();
            }
        } catch (Exception ignore) {
            //
        }
        return null;
    }

    private AccessTokenResponse getAccessTokenByCode(String code) {
        Stopwatch stopwatch = Stopwatch.createStarted();
        String url = String.format("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&" +
                "code=%s&grant_type=authorization_code", appid, secret, code);

        try {
            ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
            if (responseEntity.getStatusCodeValue() == 200 && responseEntity.getBody() != null) {
                AccessTokenResponse response = objectMapper.readValue(responseEntity.getBody(), AccessTokenResponse.class);
                if (response.getAccess_token() != null && response.getOpenid() != null) {
                    return response;
                }
            }
            log.warn("get access token failed, response: {}, code {}  cost {}", responseEntity, code, stopwatch.elapsed(TimeUnit.MILLISECONDS));
        } catch (Exception e) {
            log.warn("get access token failed", e);
        }
        throw new ServiceException("登录失败，请重新访问");
    }

    private String getAppAccessToken() {
        String url = String.format("https://api.weixin.qq.com/cgi-bin/token?appid=%s&secret=%s&" +
                "grant_type=client_credential", appid, secret);

        try {
            ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
            if (responseEntity.getStatusCodeValue() == 200 && responseEntity.getBody() != null) {
                AccessTokenResponse response = objectMapper.readValue(responseEntity.getBody(), AccessTokenResponse.class);
                if (response.getAccess_token() != null) {
                    return response.getAccess_token();
                }
            }
            log.error("get access token failed, response: {}", responseEntity);
        } catch (Exception e) {
            log.error("get access token failed", e);
        }
        return null;
    }

    private String getAppTicket(String accessToken) {
        String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + accessToken + "&type=jsapi";

        try {
            ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
            if (responseEntity.getStatusCodeValue() == 200 && responseEntity.getBody() != null) {
                TicketResponse response = objectMapper.readValue(responseEntity.getBody(), TicketResponse.class);
                if (response.getTicket() != null) {
                    return response.getTicket();
                }
            }
            log.error("get ticket failed, response: {}", responseEntity);
        } catch (Exception e) {
            log.error("get ticket failed", e);
        }
        return null;
    }

    private UserInfoResponse getUserInfoByAccessToken(String accessToken, String openid, Integer retryCount) {
        Stopwatch stopwatch = Stopwatch.createStarted();
        String url = String.format("https://api.weixin.qq.com/cgi-bin/user/info?access_token=%s&openid=%s&lang=zh_CN",
                accessToken, openid);

        try {
            ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
            if (responseEntity.getStatusCodeValue() == 200 && responseEntity.getBody() != null) {
                UserInfoResponse response = objectMapper.readValue(responseEntity.getBody(), UserInfoResponse.class);
                if (response.getSubscribe() != null) {
                    return response;
                } else if (retryCount > 0) {
                    return getUserInfoByAccessToken(accessToken, openid, retryCount - 1);
                }
            } else {
                log.warn("get user info failed, code {}  cost {}", responseEntity, stopwatch.elapsed(TimeUnit.MILLISECONDS));
            }
        } catch (Exception e) {
            log.error("get user info failed", e);
        }
        return null;
    }

    @Data
    private static class AccessTokenResponse {

        private String access_token;

        private Integer expires_in;

        private String refresh_token;

        private String openid;

        private String scope;
    }

    @Data
    private static class TicketResponse {

        private String errcode;

        private String errmsg;

        private String ticket;

        private Integer expires_in;
    }

    @Data
    private static class UserInfoResponse {

        private Integer subscribe;

        private String openid;

        private String nickname;

        private Integer sex;

        private String language;

//        private String city;
//
//        private String province;
//
//        private String country;

        private String headimgurl;
    }

}
